﻿using System;
using System.Windows;
using System.Windows.Shapes;
using GuiLabs.Undo;

namespace DynamicGeometry
{
    public abstract partial class FigureCreator : Behavior
    {
        #region State machine

        public override void Started()
        {
            Reset();
        }

        public override void Stopping()
        {
            Rollback();
        }

        protected override void Reset()
        {
            
            Drawing.Figures.ClearSelection();
            Drawing.Figures.EnableAll();
            TempResult = null;
            RemoveIntermediateFigureIfNecessary();
            RemoveTempPointIfNecessary();
            Transaction = CreateTransaction();
            ExpectedDependencies = InitExpectedDependencies();
            InitFoundDependencies();
        }

        protected void Finish()
        {
            if (TempResult == null)
            {
                CreateAndAddFigure();
            }
            Drawing.Recalculate();
            Commit();
            Drawing.RaiseConstructionStepComplete(new Drawing.ConstructionStepCompleteEventArgs()
            {
                ConstructionComplete = true
            });
            //Drawing.SetDefaultBehavior();
            
            //Behavior.NewBehaviorCreated(Dragger);
            Reset();
            Drawing.SetDefaultBehavior();
        }

        #endregion

        #region Transaction

        public Transaction Transaction { get; set; }

        public Transaction CreateTransaction()
        {
            return Transaction.Create(Drawing.ActionManager, false);
        }

        public void Commit()
        {
            if (Transaction != null)
            {
                Transaction.Commit();
                Transaction = null;
            }
        }

        public void Rollback()
        {
            if (Transaction != null)
            {
                Transaction.Rollback();
                Transaction = null;
            }
            RemoveTempPointIfNecessary();
            RemoveTempResultIfNecessary();
            RemoveIntermediateFigureIfNecessary();
            Drawing.RaiseConstructionStepComplete(new Drawing.ConstructionStepCompleteEventArgs() { ConstructionComplete = true });
        }

        #endregion

        #region Intermediate results

        public IFigure TempResult { get; set; }
        public FreePoint TempPoint { get; set; }
        public IFigure IntermediateFigure { get; set; }

        void SetTempPointStyle(Shape shape)
        {
            shape.IsHitTestVisible = false;
            shape.Opacity = 0.5;
        }

        void ClearTempPointStyle(Shape shape)
        {
            shape.ClearValue(UIElement.IsHitTestVisibleProperty);
            shape.ClearValue(UIElement.OpacityProperty);
        }

        private FreePoint CreateTempPoint(Point coordinates)
        {
            var result = Factory.CreateFreePoint(Drawing, coordinates);
            TempPointCreationAction = new AddFigureAction(Drawing, result);
            Drawing.ActionManager.RecordAction(TempPointCreationAction);
            SetTempPointStyle(result.Shape);
            return result;
        }

        AddFigureAction TempPointCreationAction;

        void DeleteTempPoint()
        {
            var tx = Drawing.ActionManager.RecordingTransaction;
            if (tx != null)
            {
                tx.Remove(TempPointCreationAction);
            }
            if (TempPointCreationAction != null)
            {
                TempPointCreationAction.UnExecute();
                TempPointCreationAction = null;
            }
        }

        private void AddIntermediateFigureIfNecessary()
        {
            if (TempResult == null)
            {
                IntermediateFigure = CreateIntermediateFigure();
                if (IntermediateFigure != null)
                {
                    Drawing.Figures.Add(IntermediateFigure);
                }
            }
        }

        protected virtual IFigure CreateIntermediateFigure()
        {
            return null;
        }

        protected virtual void RemoveIntermediateFigureIfNecessary()
        {
            if (IntermediateFigure != null)
            {
                Drawing.Figures.Remove(IntermediateFigure);
                IntermediateFigure = null;
            }
        }

        protected virtual void RemoveTempResultIfNecessary()
        {
            if (TempResult != null)
            {
                Drawing.Figures.Remove(TempResult);
                TempResult = null;
            }
        }

        protected void RemoveTempPointIfNecessary()
        {
            if (TempPoint != null)
            {
                Drawing.Figures.Remove(TempPoint);
                TempPoint = null;
            }
        }

        #endregion

        #region Creating the figure

        protected abstract IFigure CreateFigure();

        protected virtual void CreateAndAddFigure()
        {
            TempResult = CreateFigure();
            Drawing.Add(TempResult);
        }

        #endregion

        #region Found & next dependencies

        protected void InitFoundDependencies()
        {
            FoundDependencies = new FigureList();
        }

        private DependencyList ExpectedDependencies { get; set; }
        protected FigureList FoundDependencies { get; set; }

        protected virtual Type ExpectedDependency
        {
            get
            {
                if (FoundDependencies.Count < ExpectedDependencies.Count)
                {
                    return ExpectedDependencies[FoundDependencies.Count];
                }
                return null;
            }
        }

        protected virtual bool ExpectingAPoint()
        {
            return typeof(IPoint).IsAssignableFrom(ExpectedDependency);
        }

        private void AdvertiseNextDependency()
        {
            var nextDependency = ExpectedDependency;
            if (nextDependency == null && TempPoint != null)
            {
                nextDependency = typeof(IPoint);
            }
            Drawing.RaiseConstructionStepComplete(new Drawing.ConstructionStepCompleteEventArgs()
            {
                ConstructionComplete = false,
                FigureTypeNeeded = nextDependency
            });
        }

        protected virtual bool CanReuseDependency()
        {
            return false;
        }

        protected abstract DependencyList InitExpectedDependencies();

        protected virtual void AddFoundDependency(IFigure figure)
        {
            if (figure != null && ExpectedDependency.IsAssignableFrom(figure.GetType()))
            {
                FoundDependencies.Add(figure);
            }
        }

        #endregion

        #region State machine transition on clicking

        /// <summary>
        /// Assumes coordinates are logical already
        /// </summary>
        /// <param name="coordinates">Logical coordinates of the click point</param>
        protected virtual void Click(Point coordinates)
        {
            IFigure underMouse = null;

            if (TempPoint != null)
            {
                underMouse = Drawing.Figures.HitTest(coordinates, typeof(IPoint));
            }
            else if (ExpectedDependency != null)
            {
                underMouse = LookForExpectedDependencyUnderCursor(coordinates);
            }
            else
            {
                underMouse = Drawing.Figures.HitTest(coordinates);
            }

            if (underMouse != null
                && underMouse != TempPoint
                && ((FoundDependencies.Contains(underMouse) && !CanReuseDependency())
                    || underMouse == TempResult))
            {
                return;
            }

            if (ExpectingAPoint() && underMouse == null)
            {
                underMouse = CreatePointAtCurrentPosition(coordinates, true);
            }

            Drawing.RaiseConstructionStepStarted();

            RemoveIntermediateFigureIfNecessary();

            if (TempPoint != null)
            {
                if (underMouse != TempPoint && underMouse != TempResult && underMouse != null)
                {
                    TempPoint.SubstituteWith(underMouse);
                    FoundDependencies.Remove(TempPoint);
                    DeleteTempPoint();
                }
                ClearTempPointStyle(TempPoint.Shape);
                TempPoint = null;
            }

            if (ExpectedDependency != null)
            {
                AddFoundDependency(underMouse);
            }

            if (ExpectedDependency != null)
            {
                if (ExpectingAPoint())
                {
                    TempPoint = CreateTempPoint(coordinates);
                    AddFoundDependency(TempPoint);
                    if (ExpectedDependency == null)
                    {
                        CreateAndAddFigure();
                    }
                }
                AddIntermediateFigureIfNecessary();
                AdvertiseNextDependency();
            }
            else
            {
                Finish();
            }

            Drawing.Figures.CheckConsistency();
        }

        protected virtual IFigure LookForExpectedDependencyUnderCursor(Point coordinates)
        {
            return Drawing.Figures.HitTest(coordinates, ExpectedDependency);
        }

        #endregion

        #region MouseDown, MouseMove, MouseUp

        Point MouseDownCoordinates;
        public bool IsMouseButtonDown { get; set; }

        public override void MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            IsMouseButtonDown = true;
            Point newPosition = Coordinates(e);
            MouseDownCoordinates = newPosition;
            Click(MouseDownCoordinates);
        }

        public override void MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (TempPoint != null)
            {
                Point newPosition = Coordinates(e);
                if (TempResult != null && TempResult is LineBase)
                {
                    if (Settings.Instance.EnableOrtho)
                    {
                        newPosition = Math.GetOrthoPosition(((LineBase)TempResult).Coordinates.P1, newPosition);
                    }
                }
                TempPoint.MoveTo(newPosition);
                Drawing.Recalculate();
            }
            Drawing.RaiseConstructionFeedback(new Drawing.ConstructionFeedbackEventArgs()
            {
                FigureTypeNeeded = ExpectedDependency,
                IsMouseButtonDown = IsMouseButtonDown
            });
        }

        public override void MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            var coordinates = Coordinates(e);
            IsMouseButtonDown = false;
            if (TempPoint != null && coordinates.Distance(MouseDownCoordinates) > 3 * CursorTolerance)
            {
                Click(coordinates);
            }
        }

        #endregion

        #region KeyDown

        public override void KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == System.Windows.Input.Key.Escape)
            {
                if (FoundDependencies.IsEmpty())
                {
                    AbortAndSetDefaultTool();
                }
                else
                {
                    Cancel();
                }
                e.Handled = true;
            }
        }

        #endregion
    }
}